﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Exceptions;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;
using UtilZ.Dotnet.WindowsDesktopEx.NativeMethod;

namespace UtilZ.Dotnet.WindowsDesktopEx.Base
{

    /// <summary>
    /// 嵌套应用程序基类
    /// </summary>
    public abstract class AppEmbedderAbs : IDisposable
    {
        static AppEmbedderAbs()
        {
            if (Environment.OSVersion.Version.Major >= 6)
            {
                DesktopNativeMethods.SetProcessDPIAware();
            }
        }

        private readonly bool _startFlag;
        private bool _hasKillProcess = false;
        private bool _started = false;
        private EmbedContainerInfo _containerInfo = null;
        private int _srcStyle = 0;
        private WindowLPRECT? _srcWindowLocal = null;

        private IntPtr _mainWindowHandle = IntPtr.Zero;

        /// <summary>
        /// 获取主窗口句柄
        /// </summary>
        public IntPtr MainWindowHandle
        {
            get { return this._mainWindowHandle; }
        }

        private EncodingOptions _windowNameEncoding = EncodingOptions.Unicode;
        /// <summary>
        /// 获取或设置窗口名称编码
        /// </summary>
        public EncodingOptions WindowNameEncoding
        {
            get { return this._windowNameEncoding; }
            set { this._windowNameEncoding = value; }
        }

        private string _mainWindowName = null;
        /// <summary>
        /// 获取或设置主窗口名称,非null时有效.适用于某些主窗非显示的程序,且只一个窗口标题为此值,当有多个时请使用Filter事件选择
        /// </summary>
        public string MainWindowName
        {
            get { return this._mainWindowName; }
            set { this._mainWindowName = value; }
        }

        private int _startOrCloseProcessMillisecondsTimeout = 300;
        /// <summary>
        /// 启动或关闭进程超时时长,单位:毫秒.只对启动进程有效.
        /// </summary>
        public int StartOrCloseProcessMillisecondsTimeout
        {
            get { return this._startOrCloseProcessMillisecondsTimeout; }
            set { this._startOrCloseProcessMillisecondsTimeout = value; }
        }

        private int _mainWindowHandleIndex = 1;
        /// <summary>
        /// 从进程主窗口句柄从第一个非IntPtr.Zero开始计算,使用第几个主窗口句柄,从1开始.
        /// 对于启动就是主窗口的程序为1,当启动有登录或者lisence验证这类操作的程序嵌入，此值为进入真正主窗口,一般为2
        /// </summary>
        public int MainWindowHandleIndex
        {
            get { return this._mainWindowHandleIndex; }
            set { this._mainWindowHandleIndex = value; }
        }

        private int _getMainWindowHandleMillisecondsTimeout = 3000;
        /// <summary>
        /// 进程启动超后取进程主窗口句柄超时时长,单位:毫秒.只对启动进程有效.
        /// </summary>
        public int GetMainWindowHandleMillisecondsTimeout
        {
            get { return this._getMainWindowHandleMillisecondsTimeout; }
            set { this._getMainWindowHandleMillisecondsTimeout = value; }
        }

        private int _waitForExitMillisecondsTimeout = 500;
        /// <summary>
        /// 等待进程结束超时毫秒数,超时嵌入的进程未结束，则杀死该进程.
        /// </summary>
        public int WaitForExitMillisecondsTimeout
        {
            get { return this._waitForExitMillisecondsTimeout; }
            set { this._waitForExitMillisecondsTimeout = value; }
        }

        private bool _showCaption = false;
        /// <summary>
        /// 显示标题栏[true:显示标题栏;false:不显示标题栏]
        /// </summary>
        public bool ShowCaption
        {
            get { return this._showCaption; }
            set
            {
                if (this._showCaption == value)
                {
                    return;
                }

                this._showCaption = value;

                if (this._started)
                {
                    this.SetTitleBar(value);
                }
            }
        }

        private Point _embedWindowOffset = new Point(0, 0);
        /// <summary>
        /// 嵌入窗口位置偏移值
        /// </summary>
        public Point EmbedWindowOffset
        {
            get { return this._embedWindowOffset; }
            set { this._embedWindowOffset = value; }
        }



        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="process">嵌入进程</param>
        protected AppEmbedderAbs(Process process)
        {
            if (process == null)
            {
                throw new ArgumentException(nameof(process));
            }

            if (process.HasExited)
            {
                throw new ArgumentException($"要嵌入的进程已关闭", nameof(process));
            }

            this._process = process;
            this._startFlag = false;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="exePath">嵌入程序启动路径</param>
        /// <param name="findProcess">是否从进程列表中查找进程[true:从进程列表中查找,找到至少一个后,用找到的第一个进程;false:不从进程列表中查找,始终新启动一个进程]</param>
        /// <param name="arguments">启动参数</param>
        protected AppEmbedderAbs(string exePath, bool findProcess, string arguments)
        {
            if (string.IsNullOrEmpty(exePath))
            {
                throw new ArgumentException(nameof(exePath));
            }

            if (!System.IO.File.Exists(exePath))
            {
                throw new ArgumentException($"要嵌入的启动程序路径\"{exePath}\"不存在", nameof(exePath));
            }

            if (findProcess)
            {
                var list = ProcessEx.FindProcessByFilePath(exePath);
                if (list != null && list.Count > 0)
                {
                    this._process = list.First();
                }
            }

            if (this._process == null)
            {
                this._process = new Process()
                {
                    StartInfo = new ProcessStartInfo()
                    {
                        FileName = exePath,
                        UseShellExecute = true,
                        Arguments = arguments,
                    }
                };
                this._startFlag = true;
            }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="processStartInfo">嵌入程序进程启动信息</param>
        /// <param name="findProcess">是否从进程列表中查找进程[true:从进程列表中查找,找到至少一个后,用找到的第一个进程;false:不从进程列表中查找,始终新启动一个进程]</param>
        protected AppEmbedderAbs(ProcessStartInfo processStartInfo, bool findProcess)
        {
            if (processStartInfo == null)
            {
                throw new ArgumentException(nameof(processStartInfo));
            }

            if (string.IsNullOrEmpty(processStartInfo.FileName))
            {
                throw new ArgumentException(nameof(processStartInfo.FileName));
            }
            if (findProcess)
            {
                var list = ProcessEx.FindProcessByFilePath(processStartInfo.FileName);
                if (list != null && list.Count > 0)
                {
                    this._process = list.First();
                }
            }

            if (this._process == null)
            {
                this._process = new Process()
                {
                    StartInfo = processStartInfo
                };
                this._startFlag = true;
            }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="mainWindowHandle">主窗口句柄</param>
        protected AppEmbedderAbs(IntPtr mainWindowHandle)
        {
            if (mainWindowHandle == IntPtr.Zero)
            {
                throw new ArgumentException(nameof(mainWindowHandle));
            }

            int processId;
            int threadId = WindowHelper.GetWindowThreadProcessId(mainWindowHandle, out processId);
            this._process = Process.GetProcessById(processId);
            this._mainWindowHandle = mainWindowHandle;
            this._startFlag = false;
        }









        /// <summary>
        /// 移除窗口边框消息
        /// </summary>
        private const int GWL_STYLE = (-16);





        /// <summary>
        /// 窗口控件中的应用程序进程
        /// </summary>
        private readonly Process _process = null;

        /// <summary>
        /// 获取窗口控件中的应用程序进程
        /// </summary>
        public Process Process
        {
            get { return this._process; }
        }

        /// <summary>
        /// 进程结束通知事件
        /// </summary>
        public event EventHandler Exited;

        private bool _visible = true;
        /// <summary>
        /// 获取或设置当前嵌套进程窗口可见性[true:显示;false:隐藏]
        /// </summary>
        public bool Visible
        {
            get { return this._visible; }
            set
            {
                this._visible = value;

                if (this._process == null || this._mainWindowHandle == IntPtr.Zero)
                {
                    return;
                }

                DesktopNativeMethods.ShowWindow(this._mainWindowHandle, value ? 1 : 0);
            }
        }


        /// <summary>
        /// 从句柄名列表中选择出主窗口句柄
        /// </summary>
        public event EventHandler<MainWindowHandleResolveEventArgs> Filter;

        /// <summary>
        /// 在窗口控件中启动应用程序
        /// </summary>
        public void Start()
        {
            if (this._startFlag)
            {
                //启动进程
                this._process.Start();
                this._hasKillProcess = true;

                // 使用超时机制等待窗口句柄创建，避免无限等待  
                bool idleStatus = this._process.WaitForInputIdle(this._startOrCloseProcessMillisecondsTimeout);

                //等待第一个主窗口句柄出现
                while (this._process.MainWindowHandle == IntPtr.Zero)
                {
                    // 保持UI响应  
                    this.DoEvents();
                }
            }

            //等待主窗口句柄
            this._mainWindowHandle = this.GetMainWindowHandle(this._process);
            if (this._mainWindowHandle == IntPtr.Zero)
            {
                throw new OperationFailException("获取主窗口句柄失败.");
            }

            this._srcWindowLocal = WindowHelper.GetWindowRect(this._mainWindowHandle);

            this._containerInfo = this.GetEmbedContainerInfo();

            //设置嵌入窗口父容器
            WindowHelper.SetParent(this._mainWindowHandle, this._containerInfo.ContainerHandle);

            //设置嵌入窗口位置和大小
            this.SetWindowSize(this._containerInfo.Width, this._containerInfo.Height);

            // 设置窗口样式  
            this._srcStyle = DesktopNativeMethods.GetWindowLong(this._mainWindowHandle, GWL_STYLE);
            this.SetTitleBar(this._showCaption);

            this.OnStarted(this._mainWindowHandle);

            if (!this._visible)
            {
                DesktopNativeMethods.ShowWindow(this._mainWindowHandle, 0);
            }

            this._process.Exited += this.Process_Exited;
            this._started = true;
        }


        private IntPtr GetMainWindowHandle(Process process)
        {
            //进程未启动报异常,启动关闭则为true
            if (process.HasExited)
            {
                return IntPtr.Zero;
            }

            int mainWindowHandleIndex = this._mainWindowHandleIndex;
            if ((DateTime.Now - process.StartTime).TotalMilliseconds >= this._getMainWindowHandleMillisecondsTimeout)
            {
                //当启动时长超过获取超时时长后，直接取最新的句柄就行
                mainWindowHandleIndex = 1;
            }

            IntPtr lastMainWindowHandle = IntPtr.Zero;
            process.Refresh();
            IntPtr mainWindowHandle = process.MainWindowHandle;
            var startTimestamp = TimeEx.GetTimestamp();
            //注:FindWindow是第一个窗口句柄,而有的app虽然在启动过程中会有多个窗口依次出现,但窗口标题都相同,所以下面这种方法不行,
            //mainWindowHandle = DesktopNativeMethods.FindWindow(null, this._mainWindowName);

            while (true)
            {
                if (mainWindowHandle != IntPtr.Zero && mainWindowHandle != lastMainWindowHandle)
                {
                    //ZLoger.Info($"Index:{mainWindowHandleIndex},{mainWindowHandle.ToString()}");

                    mainWindowHandleIndex--;
                    if (mainWindowHandleIndex <= 0)
                    {
                        break;
                    }
                    else
                    {
                        lastMainWindowHandle = mainWindowHandle;
                    }
                }

                if (TimeEx.GetTimestamp() - startTimestamp <= this._getMainWindowHandleMillisecondsTimeout)
                {
                    //获取主窗口句柄未超时处理

                    process.Refresh();//必须刷新,这是关键的一条,否则process.MainWindowHandle永远都是启动时的第一个主窗口句柄,后续不会变化,甚至还是IntPtr.Zero.
                    mainWindowHandle = process.MainWindowHandle;
                    this.DoEvents();// 保持UI响应  

                    if (mainWindowHandle == lastMainWindowHandle)
                    {
                        mainWindowHandle = this.WindowsApiGetMainWindowHandle(process, false);
                    }
                }
                else
                {
                    //获取主窗口句柄超时处理

                    mainWindowHandle = this.WindowsApiGetMainWindowHandle(process, true);
                    if (mainWindowHandle == IntPtr.Zero)
                    {
                        throw new TimeoutException("无法获取进程主窗口句柄");
                    }
                    else
                    {
                        break;
                    }
                }
            }

            //ZLoger.Info($"mainWindowHandle:{mainWindowHandle.ToString()}");
            return mainWindowHandle;
        }

        private IntPtr WindowsApiGetMainWindowHandle(Process process, bool timeouted)
        {
            List<NameValue<IntPtr>> processWindowHanlerList = WindowHelper.GetProcessWindowHandles(process, this._windowNameEncoding);

            //注:只有一个主窗口句柄,也需要验证,不能默认使用第一个,很有可能不是想要的
            //if (processWindowHanlerList.Count == 1)
            //{
            //    //只有一个主窗口句柄,就是它了
            //    return processWindowHanlerList[0].Value;
            //}

            //通过主窗口名称选择
            if (this._mainWindowName != null)
            {
                var retItemArr = processWindowHanlerList.Where(t => { return string.Equals(t.Name, this._mainWindowName); }).ToArray();
                if (retItemArr != null && retItemArr.Length == 1)
                {
                    return retItemArr[0].Value;
                }
            }

            //通过外部过滤选择
            var handler = this.Filter;
            if (handler != null)
            {
                var args = new MainWindowHandleResolveEventArgs(timeouted, processWindowHanlerList);
                handler(this, args);
                if (args.MainWindowHandle != IntPtr.Zero)
                {
                    return args.MainWindowHandle;
                }
            }

            return IntPtr.Zero;
        }


        /// <summary>
        /// 保持UI响应
        /// </summary>
        protected virtual void DoEvents()
        {

        }

        private void SetTitleBar(bool showCaption)
        {
            if (showCaption)
            {
                //const int WS_CHILD = 0x40000000;
                //const int WS_VISIBLE = 0x00C00000;

                //int style = DesktopNativeMethods.GetWindowLong(this._mainWindowHandle, GWL_STYLE);
                //style |= WS_CHILD | WS_VISIBLE; // 添加WS_VISIBLE确保窗口可见  
                //DesktopNativeMethods.SetWindowLong(this._mainWindowHandle, GWL_STYLE, style);

                //style |= WS_VISIBLE;
                //DesktopNativeMethods.SetWindowLong(this._mainWindowHandle, GWL_STYLE, style);
                //TaskbarManager.SetWindowTaskbarVisibility(this._mainWindowHandle, true);
                WindowHelper.SetWindowLongPtr(this._mainWindowHandle, GWL_STYLE, this._srcStyle);
            }
            else
            {
                // 移除边框和标题栏
                const int WS_HIDEN = 0x10000000;//移除窗口边框样式值
                this._srcStyle = WindowHelper.SetWindowLongPtr(this._mainWindowHandle, GWL_STYLE, WS_HIDEN);
                //TaskbarManager.SetWindowTaskbarVisibility(this._mainWindowHandle, false);
            }
        }

        private void Process_Exited(object sender, EventArgs e)
        {
            this._hasKillProcess = false;
            this._mainWindowHandle = IntPtr.Zero;
            this._process.Exited -= this.Process_Exited;

            var handler = this.Exited;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }


        internal abstract EmbedContainerInfo GetEmbedContainerInfo();

        /// <summary>
        /// 进程启动完成
        /// </summary>
        /// <param name="mainWindowHandle">嵌套进程主窗口句柄</param>
        protected abstract void OnStarted(IntPtr mainWindowHandle);



        /// <summary>
        /// 设置窗口嵌入位置和大小
        /// </summary>
        /// <param name="width">新宽度</param>
        /// <param name="height">新高度</param>
        protected void SetWindowSize(int width, int height)
        {
            if (this._embedWindowOffset.X != 0 || this._embedWindowOffset.Y != 0)
            {
                DesktopNativeMethods.MoveWindow(this._mainWindowHandle, this._containerInfo.X + this._embedWindowOffset.X, this._containerInfo.Y - this._embedWindowOffset.Y, width - this._embedWindowOffset.X, height + this._embedWindowOffset.Y, true);
            }
            else
            {
                DesktopNativeMethods.MoveWindow(this._mainWindowHandle, this._containerInfo.X, this._containerInfo.Y, width, height, true);
            }
        }

        /// <summary>
        /// 窗口控件所在窗口关闭事件
        /// </summary>
        protected void ContainerWindowClosing()
        {
            this.CloseEmbedApp();
        }



        private void CloseEmbedApp()
        {
            this.Detach();

            if (this._hasKillProcess && !this._process.HasExited)
            {
                this._hasKillProcess = false;

                //有的进程会弹出确认退出框,因为没人点会一直阻塞,所以直接杀死进程
                this._process.CloseMainWindow();
                using (var eventHandler = new AutoResetEvent(false))
                {
                    Task.Factory.StartNew(() =>
                    {
                        try
                        {
                            if (this._process.WaitForExit(this._waitForExitMillisecondsTimeout))
                            {
                                eventHandler.Set();
                            }
                        }
                        catch (ObjectDisposedException)
                        {

                        }
                        catch (Exception)
                        {

                        }
                    });

                    if (!eventHandler.WaitOne(this._waitForExitMillisecondsTimeout + 10))
                    {
                        this._process.Kill();
                    }
                }

                // Post a colse message
                //Qianru.PostMessage(this._mainWindowHandle, 0x10, 0, 0);
                //if (!this._process.WaitForExit(this._startOrCloseProcessMillisecondsTimeout))
                //{
                //    this._process.Kill();
                //}
            }
        }


        /// <summary>
        /// 附加嵌入进程
        /// </summary>
        public void Attach()
        {
            if (this._mainWindowHandle != IntPtr.Zero)
            {
                return;
            }

            this._mainWindowHandle = this.GetMainWindowHandle(this._process);
            this._containerInfo = this.GetEmbedContainerInfo();
            //设置嵌入窗口位置和大小
            this.SetWindowSize(this._containerInfo.Width, this._containerInfo.Height);

            this._process.Exited += this.Process_Exited;
            WindowHelper.SetParent(this._mainWindowHandle, this._containerInfo.ContainerHandle);
        }

        /// <summary>
        /// 分离嵌入进程
        /// </summary>
        public void Detach()
        {
            if (this._mainWindowHandle == IntPtr.Zero)
            {
                return;
            }

            this._process.Exited -= this.Process_Exited;

            try
            {
                WindowHelper.SetParent(this._mainWindowHandle, IntPtr.Zero);
                if (!this._showCaption)
                {
                    this.SetTitleBar(true);
                }

                if (this._srcWindowLocal.HasValue)
                {
                    var local = this._srcWindowLocal.Value;
                    DesktopNativeMethods.MoveWindow(this._mainWindowHandle, local.Left, local.Top, local.Width, local.Height, true);
                }
                else
                {
                    this._containerInfo = this.GetEmbedContainerInfo();
                    DesktopNativeMethods.MoveWindow(this._mainWindowHandle, this._containerInfo.X + 10, this._containerInfo.Y + 10, this._containerInfo.Width, this._containerInfo.Height, true);
                }
            }
            catch (OperationFailException ofex)
            {
                ZLoger.Error(ofex);
            }

            this._mainWindowHandle = IntPtr.Zero;
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            this.CloseEmbedApp();

            this.Dispose(true);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {

        }

    }

    internal class TaskbarManager
    {
        // 获取窗口句柄的API（用于非Form窗口）
        [DllImport("user32.dll")]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        // 设置窗口属性的API
        [DllImport("user32.dll")]
        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

        // 获取窗口属性的API
        [DllImport("user32.dll")]
        private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        // 窗口样式常量（用于隐藏任务栏图标）
        private const int GWL_EXSTYLE = -20;
        private const int WS_EX_TOOLWINDOW = 0x00000080;
        private const int WS_EX_APPWINDOW = 0x00040000;

        // 通过Form对象控制任务栏显示（适用于Windows Forms应用）
        public static void SetFormTaskbarVisibility(Form form, bool showInTaskbar)
        {
            form.ShowInTaskbar = showInTaskbar;
            // 若隐藏后需要重新显示，需确保窗口可见性同步调整
            if (showInTaskbar && !form.Visible)
                form.Show();
        }

        /// <summary>
        /// 通过窗口句柄控制任务栏显示（适用于非Form窗口或外部进程）
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="showInTaskbar"></param>
        public static void SetWindowTaskbarVisibility(IntPtr hWnd, bool showInTaskbar)
        {
            //IntPtr hWnd = FindWindow(null, windowTitle);
            if (hWnd == IntPtr.Zero)
            {
                Console.WriteLine("未找到窗口句柄");
                return;
            }

            int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
            if (showInTaskbar)
            {
                // 显示任务栏图标：移除工具窗口样式，添加应用窗口样式
                exStyle = exStyle & ~WS_EX_TOOLWINDOW;
                exStyle = exStyle | WS_EX_APPWINDOW;
            }
            else
            {
                // 隐藏任务栏图标：添加工具窗口样式，移除应用窗口样式
                exStyle = exStyle | WS_EX_TOOLWINDOW;
                exStyle = exStyle & ~WS_EX_APPWINDOW;
            }
            SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
        }
    }

    /// <summary>
    /// 主窗口句柄反选事件参数
    /// </summary>
    public class MainWindowHandleResolveEventArgs : EventArgs
    {
        /// <summary>
        /// 超时标识[true:超时;false:正常获取]
        /// </summary>
        public bool Timeouted { get; private set; }

        /// <summary>
        /// 句柄名称列表
        /// </summary>
        public List<NameValue<IntPtr>> WindowHandleNames { get; private set; }

        /// <summary>
        /// 主窗口句柄
        /// </summary>
        public IntPtr MainWindowHandle { get; set; } = IntPtr.Zero;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="timeouted">超时标识[true:超时;false:正常获取]</param>
        /// <param name="windowHandleNames">句柄名称列表</param>
        public MainWindowHandleResolveEventArgs(bool timeouted, List<NameValue<IntPtr>> windowHandleNames)
        {
            this.Timeouted = timeouted;
            this.WindowHandleNames = windowHandleNames;
        }

    }

    internal class EmbedContainerInfo
    {
        public readonly IntPtr ContainerHandle;
        public readonly int X;
        public readonly int Y;
        public readonly int Width;
        public readonly int Height;


        public EmbedContainerInfo(IntPtr containerHandle, int x, int y, int width, int height)
        {
            this.ContainerHandle = containerHandle;
            this.X = x;
            this.Y = y;
            this.Width = width;
            this.Height = height;
        }

    }

}
